require("lua_mysql")

--== SqlType ==--
SqlType = { UNKNOWN 		= 0,
			BIT 			= 1,
			TINYINT 		= 2,
			SMALLINT 		= 3,
			MEDIUMINT 		= 4,
			INTEGER 		= 5,
			BIGINT			= 6,
			REAL			= 7,
			DOUBLE			= 8,
			DECIMAL			= 9,
			NUMERIC			= 10,
			CHAR			= 11,
			BINARY			= 12,
			VARCHAR			= 13,
			VARBINARY		= 14,
			LONGVARCHAR		= 15,
			LONGVARBINARY	= 16,
			TIMESTAMP		= 17,
			DATE			= 18,
			TIME			= 19,
			YEAR			= 20,
			GEOMETRY		= 21,
			ENUM			= 22,
			SET				= 23,
			SQLNULL			= 24 }

--== getMysqlDriverInstance ==--
function getMysqlDriverInstance()
 	local driver = MysqlSqlDriver.getDriverInstance(MysqlExtConfig)

 	return driver
end

--== string trim ==--
function trim(str)
  local s = str:match('^%s*()')
  local e = str:match('()%s*$', s)
  return str:sub(s,e-1)
end

--== StingBuffer ==--
StingBufferMeta = {}
StingBufferMeta.__index = StingBufferMeta

function StingBufferMeta:append(...)
	if arg.n == 1 then
		self[#self + 1] = arg[1]
	elseif arg.n > 1 then
		for i,v in ipairs(arg) do
			self[#self + 1] = v
		end
	end
end

function StingBufferMeta:__tostring()
	return self:toString()
end

function StingBufferMeta:toString()
	return table.concat(self)
end

function StringBuffer()
	local sb = {}
	setmetatable(sb, StingBufferMeta)

	return sb
end

--== compileSqlString ==--
PlaceholderMapMeta = {}
function PlaceholderMapMeta:__tostring()
	local result = StringBuffer()
	result:append("{")

	for k,v in pairs(self) do
		result:append(k, "=")
		result:append("[")
		for iv,vv in ipairs(v) do
			result:append(vv)
			if iv < #v then
				result:append(",")
			end
		end
		result:append("]")
		result:append(" ")
	end
	table.remove(result) --remove last element(here is a space char) from result
	result:append("}")
	return result:toString()
end

function findKeyInTable(placeholderMap, key)
	if placeholderMap == nil then
		return false
	end

	for k,v in pairs(placeholderMap) do
		if k == key then
			return true
		end
	end

	return false
end

function compileSqlString(str)
	local pattern1 = "(:[%w_]*)()"
	local pattern2 = ":[%w_]*"
	local init = 1
	local count = 1
	local placeholderMap = nil

	while init <= str:len() do
		local key , e = str:match(pattern1, init)
		--print (key, e)

		if not key then
			break
		end

		isExist = findKeyInTable(placeholderMap, key)
		if(isExist) then
			parray = placeholderMap[key]
			parray[#parray + 1] = count
		else
		    if placeholderMap == nil then
			  placeholderMap = {}
			end
			placeholderMap[key] = {count}
		end

		init = e
		count = count + 1
	end

	local result = str:gsub(pattern2, "?")
	if placeholderMap and type(placeholderMap) == "table" then
		setmetatable(placeholderMap, PlaceholderMapMeta)
	end

	return result, placeholderMap
end

--== test ==--
--str1 = "select * from test where id1 = :id and id2 = :id and label = :label"
--cs, ph = compileSqlString(str1)
--print(cs)
--print(ph)

--== getBaseMeta ==--
function getBaseMeta(self)
	return getmetatable(getmetatable(self))
end

--== lua_mysql extension map ==--
MysqlExtConfig = {
	Connection = {
		prepareStatement = function(self, sqlstr)
			local compiledSql, placeholderMap = sqlstr, nil
			if not sqlstr:find("?") then
				compiledSql, placeholderMap = compileSqlString(sqlstr)
			end

			local baseMeta = getBaseMeta(self)
			local ptmt = baseMeta.prepareStatement(self, compiledSql)

			ptmt.sqlstr = compiledSql
			ptmt.placeholderMap = placeholderMap

			return ptmt
		end
	},
	PreparedStatement = {
		setBigInt = function(self, index, value)
			local baseMeta = getBaseMeta(self)
			if value == nil then
				baseMeta.setNull(self, index, SqlType.SQLNULL)
			else
				baseMeta.setBigInt(self, index, value)
			end
		end,
		setTimestamp = function(self, index, value)
			local baseMeta = getBaseMeta(self)
			if value == nil then
				baseMeta.setNull(self, index, SqlType.SQLNULL)
			else
				baseMeta.setTimestamp(self, index, value)
			end
		end,
		setBoolean = function(self, index, value)
			local baseMeta = getBaseMeta(self)
			if value == nil then
				baseMeta.setNull(self, index, SqlType.SQLNULL)
			else
				baseMeta.setBoolean(self, index, value)
			end
		end,
		setDouble = function(self, index, value)
			local baseMeta = getBaseMeta(self)
			if value == nil then
				baseMeta.setNull(self, index, SqlType.SQLNULL)
			else
				baseMeta.setDouble(self, index, value)
			end
		end,
		setInt = function(self, index, value)
			local baseMeta = getBaseMeta(self)
			if value == nil then
				baseMeta.setNull(self, index, SqlType.SQLNULL)
			else
				baseMeta.setInt(self, index, value)
			end
		end,
		setNull = function(self, index, value)
			local baseMeta = getBaseMeta(self)
			baseMeta.setNull(self, index, SqlType.SQLNULL)
		end,
		setString = function(self, index, value)
			local baseMeta = getBaseMeta(self)
			if value == nil then
				baseMeta.setNull(self, index, SqlType.SQLNULL)
			else
				baseMeta.setString(self, index, value)
			end
		end,

		-----

		setParam = function(self, placeHolder, value, sqlType)
			local phmap = self.placeholderMap
			if phmap == nil then
				error("placeholderMap is invalid, can not use setParam().")
			end

			local phArray = phmap[placeHolder]
			if phArray == nil or #phArray == 0 then
				error("placeholderMap is invalid, can not use setParam().")
			end

			local baseMeta = getBaseMeta(self)

			if sqlType == nil then
				local vType = type(value)

				for i = 1, #phArray do
					if vType == "number" then
						baseMeta.setDouble(self, phArray[i], value)
					elseif vType == "string" then
						baseMeta.setString(self, phArray[i], value)
					elseif vType == "boolean" then
						baseMeta.setBoolean(self, phArray[i], value)
					elseif value == nil then
						baseMeta.setNull(self, phArray[i], SqlType.SQLNULL)
					end
				end
			else
				for i = 1, #phArray do
					if value == nil then
						baseMeta.setNull(self, phArray[i], SqlType.SQLNULL)
					elseif sqlType == SqlType.UNKNOWN then
						error("SqlType.UNKNOWN not support.")
					elseif sqlType == SqlType.BIT then
						baseMeta.setInt(self, phArray[i], value)
					elseif sqlType == SqlType.TINYINT then
						baseMeta.setInt(self, phArray[i], value)
					elseif sqlType == SqlType.SMALLINT then
						baseMeta.setInt(self, phArray[i], value)
					elseif sqlType == SqlType.MEDIUMINT then
						baseMeta.setInt(self, phArray[i], value)
					elseif sqlType == SqlType.INTEGER then
						baseMeta.setInt(self, phArray[i], value)
					elseif sqlType == SqlType.BIGINT then
						baseMeta.setBigInt(self, phArray[i], value)
					elseif sqlType == SqlType.REAL then
						baseMeta.setDouble(self, phArray[i], value)
					elseif sqlType == SqlType.DOUBLE then
						baseMeta.setDouble(self, phArray[i], value)
					elseif sqlType == SqlType.DECIMAL then
						baseMeta.setDouble(self, phArray[i], value)
					elseif sqlType == SqlType.NUMERIC then
						baseMeta.setDouble(self, phArray[i], value)
					elseif sqlType == SqlType.CHAR then
						baseMeta.setString(self, phArray[i], value)
					elseif sqlType == SqlType.BINARY then
						error("SqlType.BINARY not support.")
					elseif sqlType == SqlType.VARCHAR then
						baseMeta.setString(self, phArray[i], value)
					elseif sqlType == SqlType.VARBINARY then
						error("SqlType.VARBINARY not support.")
					elseif sqlType == SqlType.LONGVARCHAR then
						baseMeta.setString(self, phArray[i], value)
					elseif sqlType == SqlType.LONGVARBINARY then
						error("SqlType.LONGVARBINARY not support.")
					elseif sqlType == SqlType.TIMESTAMP then
						baseMeta.setTimestamp(self, phArray[i], value)
					elseif sqlType == SqlType.DATE then
						baseMeta.setTimestamp(self, phArray[i], value)
					elseif sqlType == SqlType.TIME then
						baseMeta.setTimestamp(self, phArray[i], value)
					elseif sqlType == SqlType.YEAR then
						baseMeta.setTimestamp(self, phArray[i], value)
					elseif sqlType == SqlType.GEOMETRY then
						baseMeta.setTimestamp(self, phArray[i], value)
					elseif sqlType == SqlType.ENUM then
						error("SqlType.ENUM not support.")
					elseif sqlType == SqlType.SET then
						error("SqlType.SET not support.")
					elseif sqlType == SqlType.SQLNULL then
						baseMeta.setNull(self, phArray[i], SqlType.SQLNULL)
					end
				end
			end

		end
	}
}

